import scipy
from helper_codes.general_stimulus import general_stimulus, GeneralStim

def flashing_dot_settings():
    '''
    This function tells you what settings are needed and in what order for this stimulus.
    Inputs:
        None
    Returns:
        settings        : A list of tupples.  In each tupple is
                            (setting_name, setting_description, default_value).
                          The list is of length num_settings + 1, so that the final
                             value in the list is True/False wether or not the stimulus
                             can be triggered via the parallel port or not.
    '''
    settings = []
    settings.append( ('dot_size',
                     'The size (diameter) of the flashing dot. (normalized RF units)',
                     1.0 ))
    settings.append( ('position',
                     'Where the stimulus will flash. (normalized RF units) [list]',
                     [-0.5,-0.5] ))
    settings.append( ('num_flashes',
                     'The number of times the dot will flash',
                     1.0 ))
    settings.append( ('on_duration',
                     'The amount of time the dot will be visible. (seconds)',
                     1.0 ))
    settings.append( ('off_duration',
                     'The amount of time the dot will be invisible. (seconds)',
                     1.0 ))
    settings.append( ('shade',
                     '1.0 for black dots, -1.0 for white dots.',
                     1.0 ))
    settings.append( ('mirror_pos',
                     'The position of a "mirrored" stimulus relative to the RF. (normalized RF units) [list]',
                     [0.0,0.0] ))
    settings.append( ('delay',
                     'Time between showing the mirror and the regular stimulus (seconds) negative values supported.',
                     0.0 ))
    settings.append(True) # this stimulus can be triggered
    return settings

def flashing_dot_runtime(dot_size, position, num_flashes, on_duration, off_duration,
                         shade, mirror_pos, delay):
    """
    Calculate and return the number of seconds that this stimulus will need to run, after being triggered.
    """
    return on_duration*num_flashes + off_duration*(num_flashes-1) + abs(delay) + 0.99 # 0.99 added so rounding is always up.

class Stim(GeneralStim):
    """
    This is a basic stimulus object, it must have:
        .setup(window) method to set itself up to be drawn
        .draw() method to draw itself to the display
        .evolve() method to set itself up for the next draw
        .exhausted variable set to True after stim is done.
    """
    def __init__( self, dot_size, position, num_flashes, on_duration, off_duration, 
                  shade, mirror_pos, delay, refresh_rate, preview, save_frames):
        self.dot_size = dot_size
        self.position = position
        self.num_flashes = int(num_flashes)
        self.on_duration = on_duration
        self.off_duration = off_duration
        self.shade = shade
        self.mirror_pos = scipy.array(mirror_pos)
        self.delay = delay
        self.refresh_rate = refresh_rate
        self.preview = preview
        self.save_frames = save_frames

        self.exhausted = False
        self.times_evolved = 0

    def setup(self, window):
        from psychopy import visual

        # how many frames we'll display
        self.on_frames = int(self.on_duration*self.refresh_rate)
        self.off_frames = int(self.off_duration*self.refresh_rate)
        self.delay_frames = int(abs(self.delay)*self.refresh_rate)

        # set up the positions and orientations lists
        positions = [[1e10,1e10]] # first position is an off frame
        for i in xrange(self.num_flashes-1):
            positions.extend([[self.position[0],self.position[1]] 
                              for i in xrange(self.on_frames)])
            # change the position to way way off the screen for off periods
            positions.extend([[1e10,1e10] for i in xrange(self.off_frames)])
        positions.extend([[self.position[0],self.position[1]] 
                           for i in xrange(self.on_frames)])
        positions.extend([[1e10,1e10] 
                           for i in xrange(self.off_frames)])
        positions.append([1e10,1e10]) # add one last frame of off in case there is a
                                      # mirror delay that would cause the flash to 
                                      # last too long.
        self.positions = scipy.array(positions)

        # make the textures of the dots.
        texture = scipy.ones((4,4),dtype=scipy.float64)*-1.0*self.shade
        self.dot = visual.PatchStim( window, 
                                        tex=texture, 
                                        pos=self.positions[0],
                                        size=(self.dot_size, self.dot_size), 
                                        mask='circle',
                                        units='norm')
        self.mirror_dot = visual.PatchStim( window, 
                                        tex=texture, 
                                        pos=self.positions[0],
                                        size=(self.dot_size, self.dot_size), 
                                        mask='circle',
                                        units='norm')
        self.textures = [self.dot, self.mirror_dot]


    def draw(self):
        self.dot.draw()
        self.mirror_dot.draw()

    def evolve(self):
        self.times_evolved += 1

        if self.delay < 0.0: # mirror_dot goes first
            mirror_dot_pos_i = min(len(self.positions)-1, self.times_evolved)
            dot_pos_i = max(0, self.times_evolved-self.delay_frames)
        else: # dot goes first, then mirror
            mirror_dot_pos_i = max(0, self.times_evolved-self.delay_frames)
            dot_pos_i = min(len(self.positions)-1, self.times_evolved)
        self.dot.setPos(self.positions[dot_pos_i])
        self.mirror_dot.setPos(self.positions[mirror_dot_pos_i]+self.mirror_pos)

        if self.times_evolved >= len(self.positions)-1+self.delay_frames:
            if (not self.preview) or self.save_frames:
                self.exhausted = True
            else:
                self.times_evolved = 0 # continue looping forever in preview mode.

def flashing_dot( dot_size, position, num_flashes, on_duration, off_duration, shade,
                  mirror_pos, delay, 
                     win_size = [1440,900],
                     refresh_rate = 60,
                     window=None,
                     eye_cup=None, 
                     rf=None, 
                     save_frames=False,
                     save_frames_factor=1.0,
                     save_frames_size=100,
                     display_saved_frames=False,
                     preview=True):
    """
    This stimulus shows a flashing dot
    Inputs:
        dot_size        : the size of the DIAMETER of the flashing dot in normalized units.
        position        : the x,y coordinates (normalized RF coords) of the dot
        num_flashes     : the number of flashes 
        on_duration     : how long the dot is visible per flash
        off_duration    : how long in between flashes
        shade           : 1.0 for black dots -1.0 for white dots
        mirror_pos      : the position of a "mirror" of the regular stimulus in normalized units.
        delay           : delay between regular and mirror display, negative values supported.
        preview         : If false the program will wait for a pulse trigger, otherwise it won't.
        save_frames_factor  : if None, then no frames are saved, otherwise it is a 
                                scaling factor so we can scale down the frames and
                                output a frames.gif (an animated gif file).
        eye_cup         : an Eye_cup object, see its docstring for more info.
        rf              : RF object, to set up new coordinate system.
    Returns:
        times     : The times when frames were shown, with zero being the first frame time.
    """
    stim_list = [ Stim( dot_size, position, num_flashes, on_duration, off_duration, 
                        shade, mirror_pos, delay, refresh_rate, preview, save_frames) ]
    return_values = general_stimulus( stim_list, shade, win_size, 
                                         window=window,
                                         eye_cup=eye_cup, 
                                         rf=rf, 
                                         save_frames=save_frames,
                                         save_frames_factor=save_frames_factor,
                                         save_frames_size=save_frames_size,
                                         display_saved_frames=display_saved_frames,
                                         preview=preview)
    return return_values

if __name__ == '__main__':
   
    # ---------------------------------
    # -- Set the stimulus parameters --
    # ---------------------------------
    dot_size      = 0.2      # diameter of dot in rf units
    position      = [0.5,0.5]
    num_flashes   = 5
    on_duration   = 1.0      # in seconds
    off_duration  = 1.0      # in seconds
    shade         = 1.0
    mirror_pos    = [0,-0.5]
    delay         = 1.5
    
    return_values = flashing_dot(dot_size, position, num_flashes, 
                                 on_duration, off_duration,
                                 shade, mirror_pos, delay,
                                 save_frames = False,
                                 display_saved_frames = True)

 
